#pragma once 
//服务器.hpp

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cstring>
#include <functional>
#include <cstdlib>
#include <arpa/inet.h>
#include <netinet/in.h>

#define gnum 1024

namespace Server
{
    using namespace std;

    static const string defaultIp = "0.0.0.0";//表示可以任意地址bind
    
    enum {USAGE_ERR=1,SOCKET_ERR ,BIND_ERR,OPEN_ERR};

    typedef function<void (int,string,uint16_t,string)> func_t;
    
    class udpServer
    {
    public:
        udpServer(const func_t &cb,const uint16_t &port,const string &ip = defaultIp)//ip和端口号会放入到socket_in
            :_callback(cb)
            ,_port(port)
            ,_ip(ip)
            ,_sockfd(-1)
            
        {}
        void initServer()//初始化
        {
            //1.创建socket
            _sockfd = socket(AF_INET , SOCK_DGRAM , 0);
            if(_sockfd == -1)
            {
                cerr << "sock error:" << errno << ":" <<strerror(errno) << endl;
                exit(SOCKET_ERR);
            }
            cout << "socket success: " << _sockfd << endl;

            //2.绑定port,ip
            //未来服务器要明确的port，不能随意改变，需要显示bind
            struct sockaddr_in local;
            bzero(&local,sizeof(local));
            local.sin_family=AF_INET;
            local.sin_port = htons(_port);//port和ip是要互传的，因为要“沟通”
            // local.sin_addr.s_addr = inet_addr(_ip.c_str());//1.string->uint32_t 2.htonl();-> inet_addr
            local.sin_addr.s_addr = htonl(INADDR_ANY);//任意ip bind（），INADDR_ANY（0x0000000），htonl可加可不加
            int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
            if(n==-1)
            {
                cerr << "bing error:" << errno << ":" <<strerror(errno) << endl;
                exit(BIND_ERR);
            }
            //udp server准备完成
        }
        void start()//运行
        {
            //服务器的本质其实就是一个死循环
            char buffer[gnum];
            for(;;)
            {
                struct sockaddr_in peer;
                bzero(&peer,sizeof(peer));

                socklen_t len = sizeof(peer);//必填socklen_t 是 套接字编程 中用于表示 地址结构长度 的数据类型，
                                            //通常用于 accept()、recvfrom()、getsockname()、getpeername() 等系统调用的参数中。
                ssize_t s = recvfrom(_sockfd,buffer,sizeof(buffer) - 1,0,(struct sockaddr*)&peer,&len);
                //为什么-1，C 字符串要求以 \0 结尾，但 recvfrom 不会自动添加 \0，它只是将原始数据拷贝到缓冲区。
                if( s > 0 )
                {
                    buffer[s] = 0;
                    string clientip = inet_ntoa(peer.sin_addr);//1.网络序列2．int->点分十进制IP
                    uint16_t clientport = ntohs(peer.sin_port);
                    string message = buffer;

                    // cout << clientip <<"[" << clientport << "]#" << message << endl;
                    //对读上来的数据进行处理
                    _callback(_sockfd,clientip,clientport,message);
                }
            }
        }
        ~udpServer()
        {}
    private:
        uint16_t _port;//该服务器的端口号
        string _ip;//服务器的IP地址,在实际的项目当中一般不建议指明（绑定一个ip）：可能会有多网卡的情况
        int _sockfd;//文件描述符
        func_t _callback;//回调函数
    };
}
